#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include "sysinfo.h"


#define BAD_OPEN_MESSAGE                    \
"Error: /proc must be mounted\n"                \
"  To mount /proc at boot you need an /etc/fstab line like:\n"    \
"      /proc   /proc   proc    defaults\n"            \
"  In the meantime, run \"mount /proc /proc -t proc\"\n"

#define STAT_FILE    "/proc/stat"
static int stat_fd = -1;
#define MEMINFO_FILE "/proc/meminfo"
static int meminfo_fd = -1;


#define IS_END_CHAR(ch) ( ((ch) == '\0') || ((ch) == '\r') || ((ch) == '\n') )

static char buf[2048];

#define FILE_TO_BUF(filename, fd) do{                \
    static int local_n;                        \
    if (fd == -1 && (fd = open(filename, O_RDONLY)) == -1) {    \
    fputs(BAD_OPEN_MESSAGE, stderr);            \
    fflush(NULL);                        \
    _exit(102);                        \
    }                                \
    lseek(fd, 0L, SEEK_SET);                    \
    if ((local_n = read(fd, buf, sizeof buf - 1)) < 0) {    \
    perror(filename);                    \
    fflush(NULL);                        \
    _exit(103);                        \
    }                                \
    buf[local_n] = '\0';                    \
}while(0)


static char buff[BUFFSIZE]; /* used in the procedures */

static void crash(const char *filename) {
    perror(filename);
    exit(EXIT_FAILURE);
}

void getstat(struct cpu_stat *cpu_stat) {
    static int fd;
    const char *b;
    buff[BUFFSIZE - 1] = 0;  /* ensure null termination in buffer */

    if (fd) {
        lseek(fd, 0L, SEEK_SET);
    } else {
        fd = open("/proc/stat", O_RDONLY, 0);
        if (fd == -1) crash("/proc/stat");
    }
    read(fd, buff, BUFFSIZE - 1);

    //user nice system idle iowait irq softirq
    b = strstr(buff, "cpu ");
    if (b)
        sscanf(b, "cpu  %llu %llu %llu %llu %llu %lld %lld", &cpu_stat->cpu_use, &cpu_stat->cpu_nic,
                &cpu_stat->cpu_sys, &cpu_stat->cpu_idl, &cpu_stat->cpu_iow, &cpu_stat->cpu_irq,&cpu_stat->cpu_softirq);
    //    printf("%llu %llu %llu %llu %llu %llu %llu\n",cpu_stat->cpu_use,
    //            cpu_stat->cpu_nic, cpu_stat->cpu_sys, cpu_stat->cpu_idl, cpu_stat->cpu_iow,cpu_stat->cpu_irq,cpu_stat->cpu_softirq);
}


typedef struct mem_table_struct {
    const char *name;     /* memory type name */
    unsigned long *slot; /* slot in return struct */
} mem_table_struct;

static int compare_mem_table_structs(const void *a, const void *b) {
    return strcmp(((const mem_table_struct *) a)->name, ((const mem_table_struct *) b)->name);
}


unsigned long kb_main_shared;
unsigned long kb_main_buffers;
unsigned long kb_main_cached;
unsigned long kb_main_free;
unsigned long kb_main_total;
unsigned long kb_swap_free;
unsigned long kb_swap_total;
unsigned long kb_high_free;
unsigned long kb_high_total;
unsigned long kb_low_free;
unsigned long kb_low_total;
unsigned long kb_active;
unsigned long kb_inact_laundry;
unsigned long kb_inact_dirty;
unsigned long kb_inact_clean;
unsigned long kb_inact_target;
unsigned long kb_swap_cached;  /* late 2.4 and 2.6+ only */
unsigned long kb_swap_used;
unsigned long kb_main_used;
unsigned long kb_writeback;
unsigned long kb_slab;
unsigned long nr_reversemaps;
unsigned long kb_committed_as;
unsigned long kb_dirty;
unsigned long kb_inactive;
unsigned long kb_mapped;
unsigned long kb_pagetables;
static unsigned long kb_vmalloc_chunk;
static unsigned long kb_vmalloc_total;
static unsigned long kb_vmalloc_used;
static unsigned long kb_anon_pages;
static unsigned long kb_bounce;
static unsigned long kb_commit_limit;
static unsigned long kb_nfs_unstable;
static unsigned long kb_swap_reclaimable;
static unsigned long kb_swap_unreclaimable;

void meminfo(void) {
    char namebuf[16]; /* big enough to hold any row name */
    mem_table_struct findme = {namebuf, NULL};
    mem_table_struct *found;
    char *head;
    char *tail;
    static const mem_table_struct mem_table[] = {
            {"Active",        &kb_active},       // important
            {"AnonPages",     &kb_anon_pages},
            {"Bounce",        &kb_bounce},
            {"Buffers",       &kb_main_buffers}, // important
            {"Cached",        &kb_main_cached},  // important
            {"CommitLimit",   &kb_commit_limit},
            {"Committed_AS",  &kb_committed_as},
            {"Dirty",         &kb_dirty},        // kB version of vmstat nr_dirty
            {"HighFree",      &kb_high_free},
            {"HighTotal",     &kb_high_total},
            {"Inact_clean",   &kb_inact_clean},
            {"Inact_dirty",   &kb_inact_dirty},
            {"Inact_laundry", &kb_inact_laundry},
            {"Inact_target",  &kb_inact_target},
            {"Inactive",      &kb_inactive},     // important
            {"LowFree",       &kb_low_free},
            {"LowTotal",      &kb_low_total},
            {"Mapped",        &kb_mapped},       // kB version of vmstat nr_mapped
            {"MemFree",       &kb_main_free},    // important
            {"MemShared",     &kb_main_shared},  // important, but now gone!
            {"MemTotal",      &kb_main_total},   // important
            {"NFS_Unstable",  &kb_nfs_unstable},
            {"PageTables",    &kb_pagetables},   // kB version of vmstat nr_page_table_pages
            {"ReverseMaps",   &nr_reversemaps},  // same as vmstat nr_page_table_pages
            {"SReclaimable",  &kb_swap_reclaimable}, // "swap reclaimable" (dentry and inode structures)
            {"SUnreclaim",    &kb_swap_unreclaimable},
            {"Slab",          &kb_slab},         // kB version of vmstat nr_slab
            {"SwapCached",    &kb_swap_cached},
            {"SwapFree",      &kb_swap_free},    // important
            {"SwapTotal",     &kb_swap_total},   // important
            {"VmallocChunk",  &kb_vmalloc_chunk},
            {"VmallocTotal",  &kb_vmalloc_total},
            {"VmallocUsed",   &kb_vmalloc_used},
            {"Writeback",     &kb_writeback},    // kB version of vmstat nr_writeback
    };
    const int mem_table_count = sizeof(mem_table) / sizeof(mem_table_struct);

    FILE_TO_BUF(MEMINFO_FILE, meminfo_fd);

    kb_inactive = ~0UL;

    head = buf;
    for (;;) {
        tail = strchr(head, ':');
        if (!tail)
            break;
        *tail = '\0';
        if (strlen(head) >= sizeof(namebuf)) {
            head = tail + 1;
            goto nextline;
        }
        strcpy(namebuf, head);
        found = bsearch(&findme, mem_table, mem_table_count,
                        sizeof(mem_table_struct), compare_mem_table_structs);
        head = tail + 1;
        if (!found)
            goto nextline;
        *(found->slot) = strtoul(head, &tail, 10);
        nextline:
        tail = strchr(head, '\n');
        if (!tail) break;
        head = tail + 1;
    }
    if (!kb_low_total) {  /* low==main except with large-memory support */
        kb_low_total = kb_main_total;
        kb_low_free = kb_main_free;
    }
    if (kb_inactive == ~0UL) {
        kb_inactive = kb_inact_dirty + kb_inact_clean + kb_inact_laundry;
    }
    kb_swap_used = kb_swap_total - kb_swap_free;
    kb_main_used = kb_main_total - kb_main_free;
}


unsigned int getdiskstat(struct disk_stat **disks, struct partition_stat **partitions) {
    FILE *fd;
    int cDisk = 0;
    int cPartition = 0;
    int fields;
    unsigned dummy;

    *disks = NULL;
    *partitions = NULL;
    buff[BUFFSIZE - 1] = 0;
    fd = fopen("/proc/diskstats", "rb");
    if (!fd) crash("/proc/diskstats");

    for (;;) {
        if (!fgets(buff, BUFFSIZE - 1, fd)) {
            fclose(fd);
            break;
        }
//        printf("%s\n",buff);
        fields = sscanf(buff, " %*d %*d %*s %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %u", &dummy);
//        printf("dummy = %u\n",dummy);
        if (fields == 1) {
            (*disks) = realloc(*disks, (cDisk + 1) * sizeof(struct disk_stat));
            sscanf(buff, "   %*d    %*d %15s %u %u %llu %u %u %u %llu %u %u %u %u",
                   (*disks)[cDisk].disk_name,
                   &(*disks)[cDisk].reads,
                   &(*disks)[cDisk].merged_reads,
                   &(*disks)[cDisk].reads_sectors,
                   &(*disks)[cDisk].milli_reading,
                   &(*disks)[cDisk].writes,
                   &(*disks)[cDisk].merged_writes,
                   &(*disks)[cDisk].written_sectors,
                   &(*disks)[cDisk].milli_writing,
                   &(*disks)[cDisk].inprogress_IO,
                   &(*disks)[cDisk].milli_spent_IO,
                   &(*disks)[cDisk].weighted_milli_spent_IO

            );
            (*disks)[cDisk].partitions = 0;
            cDisk++;
        } else {
            (*partitions) = realloc(*partitions, (cPartition + 1) * sizeof(struct partition_stat));
            fflush(stdout);
            sscanf(buff, "   %*d    %*d %15s %u %llu %u %u",
                    //&part_major,
                    //&part_minor,
                   (*partitions)[cPartition].partition_name,
                   &(*partitions)[cPartition].reads,
                   &(*partitions)[cPartition].reads_sectors,
                   &(*partitions)[cPartition].writes,
                   &(*partitions)[cPartition].requested_writes
            );
            (*partitions)[cPartition++].parent_disk = cDisk - 1;
            (*disks)[cDisk - 1].partitions++;
        }
    }

    return cDisk;
}


